/**
 *  Serponix is an arcade game in focus to multiplayer based on the classic game Snake.
 *  Copyright (C) 2010 - 2011  Daniel Vala
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License,
 *  or  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  If you have any question do not hesitate to contact author
 *  on e-mail address: danielvala42@gmail.com
 */
package com.serponix.net;

import com.serponix.game.Direction;
import com.serponix.game.GameModel;
import com.serponix.game.Mapa;
import com.serponix.game.Player;
import com.serponix.gui.lobby.ModelLobbyServer;
import com.serponix.settings.GameSettings;
import java.awt.Dimension;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

/**
 * Obsluhuje výměnu dat se všemi připojenými klienty
 *
 * @author Daniel Vala
 */
public class UDPServer implements Server {

	private ModelLobbyServer modelLobby;
	private DatagramSocket socket;
	private GameModel model;
	private List<Player> hraciKlienti;
	private boolean prijemLobby;
	private boolean cancel;

	public UDPServer(ModelLobbyServer modelLobby) {
		this.modelLobby = modelLobby;
		prijemLobby = true;
		hraciKlienti = new ArrayList<Player>();
		GameSettings gameSettings = GameSettings.getInstance();
		try {
			socket = new DatagramSocket(gameSettings.getPort());
		} catch (SocketException ex) {
			try {
				JOptionPane.showMessageDialog(null, "Na Vaší IP adrese " + InetAddress.getLocalHost().getHostAddress() + " a nastaveném portu " + gameSettings.getPort() + " je již hra vytvořena. \n Nebo tento port používá jiná aplikace.", "Chyba", JOptionPane.ERROR_MESSAGE);
			} catch (UnknownHostException ex2) {
				System.err.println("Chyba :" + ex2.getMessage());
			}
			System.exit(1);
		}
	}

	@Override
	public void run() {
		prijemStavuLobby();
		prijemDat();
	}

	/**
	 * Přijímá zprávy od klientů ve stavu lobby, které následně zpracuje.
	 */
	@Override
	public void prijemStavuLobby() {
		while (prijemLobby) {
			try {
				byte[] data = new byte[100];
				DatagramPacket receivedPacket = new DatagramPacket(data, data.length);
				socket.receive(receivedPacket);
				String prijataZprava;
				try {
					prijataZprava = new String(receivedPacket.getData(), 0, receivedPacket.getLength(), "UTF-8");
				} catch (UnsupportedEncodingException ex) {
					throw new RuntimeException(ex);
				}
				StringTokenizer st = new StringTokenizer(prijataZprava, Informace.ODDELOVAC1);
				int druhZpravy = 0;
				try {
					druhZpravy = Integer.parseInt(st.nextToken());
				} catch (NumberFormatException ex) {
					System.err.println("Příjem nerozeznatelného pokynu od serveru:");
					System.err.println(prijataZprava);
				}

				switch (druhZpravy) {
					case Informace.ZPRAVA:
						String informace = st.nextToken();
						zobrazInformaci(informace);
						poslatData(Informace.ZPRAVA + Informace.ODDELOVAC1 + informace);
						break;
					case Informace.NOVY_HRAC:
						Player hrac = new Player(st.nextToken(), Player.Control.CLIENT, receivedPacket);
						hraciKlienti.add(hrac);
						boolean uspech = modelLobby.addHrac(hrac);
						if (uspech) {
							String zprava = "Hráč " + hrac + " se připojil do hry.";
							zobrazInformaci(zprava);
							poslatData(Informace.ZPRAVA + Informace.ODDELOVAC1 + zprava);
							modelLobby.posliVsechnyInformace(receivedPacket);
						} else {
							poslatData(Informace.PLNY_SERVER + "");
						}
						break;
					case Informace.HLEDANI:
						byte[] zprava = (Informace.PRIJEM + Informace.ODDELOVAC1 + InetAddress.getLocalHost()).getBytes();
						socket.send(new DatagramPacket(zprava, zprava.length, receivedPacket.getAddress(), receivedPacket.getPort()));
						break;
					case Informace.KONEC:
						if (prijemLobby) {
							Player odpojenyHrac = najdiHrace(receivedPacket.getAddress());
							if (odpojenyHrac != null) {
								zobrazInformaci("Hráč " + odpojenyHrac + " se odpojil.");
								modelLobby.odstranHrace(odpojenyHrac);
								hraciKlienti.remove(odpojenyHrac);
							}
						}
						break;
					default:
						System.err.println("Příjem nerozeznatelného pokynu od serveru.");
				}
			} catch (SocketException ex) {
				if (!cancel) {
					JOptionPane.showMessageDialog(null, "Neočekávané uzavření socketu.", "Chyba spojení", JOptionPane.ERROR_MESSAGE);
				}
				return;
			} catch (IOException ex) {
				JOptionPane.showMessageDialog(null, "Chyba při čtení ze sítě.", "Chyba spojení", JOptionPane.ERROR_MESSAGE);
			}
		}
	}

	@Override
	public void setModel(GameModel model) {
		this.model = model;
	}

	@Override
	public void odstartujHru(int pocetHracu, Dimension velikostHerniPlochy, Mapa mapa) {
		prijemLobby = false;
		String zpravaKOdeslani = Informace.START + Informace.ODDELOVAC1;
		zpravaKOdeslani += Informace.POCET_HRACU + Informace.ODDELOVAC3 + pocetHracu + Informace.ODDELOVAC2;
		zpravaKOdeslani += Informace.VELIKOST_HERNI_PLOCHY + Informace.ODDELOVAC3 + (int) velikostHerniPlochy.getWidth() + Informace.ODDELOVAC3 + (int) velikostHerniPlochy.getHeight() + Informace.ODDELOVAC2;
		zpravaKOdeslani += mapa.getInformaceProKresleni() + Informace.ODDELOVAC2;
		poslatData(zpravaKOdeslani);
	}

	/**
	 * Přijímá zprávy od klientů, které následně zpracuje.
	 */
	@Override
	public void prijemDat() {
		while (true) {
			byte[] data = new byte[20000];
			DatagramPacket receivedPacket = new DatagramPacket(data, data.length);
			try {
				socket.receive(receivedPacket);
			} catch (SocketException ex) {
				if (!cancel) {
					JOptionPane.showMessageDialog(null, "Neočekávané uzavření socketu.", "Chyba spojení", JOptionPane.ERROR_MESSAGE);
					model.ukonciHru();
				}
				return;
			} catch (IOException e) {
				zobrazInformaci(e.toString());
			}
			String prijataZprava;
			try {
				prijataZprava = new String(receivedPacket.getData(), 0, receivedPacket.getLength(), "UTF-8");
			} catch (UnsupportedEncodingException ex) {
				throw new RuntimeException();
			}
			StringTokenizer st = new StringTokenizer(prijataZprava, Informace.ODDELOVAC1);
			int druhZpravy = 0;
			try {
				druhZpravy = Integer.parseInt(st.nextToken());
			} catch (NumberFormatException ex) {
				System.err.println("Příjem nerozeznatelného pokynu od serveru.");
			}

			for (int i = 0; i < hraciKlienti.size(); i++) {

				switch (druhZpravy) {
					case Informace.NAHORU:
						if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
							hraciKlienti.get(i).changeSmer(Direction.UP);
						}
						break;
					case Informace.DOLU:
						if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
							hraciKlienti.get(i).changeSmer(Direction.DOWN);
						}
						break;
					case Informace.VLEVO:
						if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
							hraciKlienti.get(i).changeSmer(Direction.LEFT);
						}
						break;
					case Informace.VPRAVO:
						if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
							hraciKlienti.get(i).changeSmer(Direction.RIGHT);
						}
						break;
					case Informace.STREL:
						if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
							hraciKlienti.get(i).getSnake().strelLaser();   // null pointer
						}
						break;
					case Informace.STRELR:
						if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
							hraciKlienti.get(i).getSnake().strelRaketu();
						}
						break;
					case Informace.PAUZA:
						if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
							hraciKlienti.get(i).pause();
						}
						break;
					case Informace.KONEC:
						if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
							Player hrac = najdiHrace(receivedPacket.getAddress());
							if (hrac != null) {
								zobrazInformaci("Hráč " + hrac + " se odpojil. Ovládání jeho hada převzala umělá inteligence.");
								hrac.zapniAI();
							}
						}
						break;
					default:
						System.err.println("Příjem nerozeznatelného pokynu od serveru.");
				}
			}
		}
	}

	@Override
	public void poslatData(String zprava) {
		byte[] data;
		try {
			data = zprava.getBytes("UTF-8");
		} catch (UnsupportedEncodingException ex) {
			throw new RuntimeException(ex);
		}
		DatagramPacket packetKOdeslani;
		for (int i = 0; i < hraciKlienti.size(); i++) {
			packetKOdeslani = new DatagramPacket(data, data.length, hraciKlienti.get(i).getPacket().getAddress(), hraciKlienti.get(i).getPacket().getPort());
			try {
				socket.send(packetKOdeslani);
			} catch (IOException ex) {
				System.err.println("IO Exception");
			}
		}
	}

	@Override
	public void odpojSe() {
		poslatData(Informace.KONEC + Informace.ODDELOVAC1);
		cancel = true;
		if (socket != null) {
			socket.close();
		}
	}

	@Override
	public void posliDataKlientovi(String zprava, DatagramPacket packet) {
		byte[] data;
		try {
			data = zprava.getBytes("UTF-8");
		} catch (UnsupportedEncodingException ex) {
			throw new RuntimeException(ex);
		}
		DatagramPacket packetKOdeslani;
		packetKOdeslani = new DatagramPacket(data, data.length, packet.getAddress(), packet.getPort());
		try {
			socket.send(packetKOdeslani);
		} catch (IOException ex) {
			System.err.println("IO Exception");
		}
	}

	private Player najdiHrace(InetAddress address) {
		for (int i = 0; i < hraciKlienti.size(); i++) {
			if (hraciKlienti.get(i).getPacket().getAddress().equals(address)) {
				return hraciKlienti.get(i);
			}
		}
		return null;
	}

	public void posliDataVsemKlientumKrome(String zprava, DatagramPacket packet) {
		byte[] data;
		try {
			data = zprava.getBytes("UTF-8");
		} catch (UnsupportedEncodingException ex) {
			throw new RuntimeException(ex);
		}
		DatagramPacket packetKOdeslani;
		for (int i = 0; i < hraciKlienti.size(); i++) {
			if (!hraciKlienti.get(i).getPacket().getAddress().equals(packet.getAddress())) {
				packetKOdeslani = new DatagramPacket(data, data.length, hraciKlienti.get(i).getPacket().getAddress(), hraciKlienti.get(i).getPacket().getPort());
				try {
					socket.send(packetKOdeslani);
				} catch (IOException ex) {
					System.err.println("IO Exception");
				}
			}
		}
	}

	/**
	 * zobrazí informaci buď v lobby, nebo ve hře
	 *
	 * @param messageToDisplay
	 */
	private void zobrazInformaci(final String messageToDisplay) {
		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				if (model != null) {
					model.getPanelZprav().addMessage(String.format(messageToDisplay));
				} else {
					modelLobby.zobrazZpravu(messageToDisplay);
				}
			}
		});
	}
}
